
#include <QRegularExpression>
#include <QString>
#include <QStringList>
#include <QDomDocument>
#include <QDomNodeList>
#include <QTcpSocket>

#include <unistd.h>

#include "applicationmanager.h"
#include "applicationconnector.h"

#include "socket.h"
#include "websocket.h"


ApplicationManager::ApplicationManager(QObject *parent) : QObject(parent),  v_logger("service_manager.log")
{
    v_logger << "=======================================================\n";
    v_logger << "=============== Service Manager Log ===================\n";
    v_logger << "=======================================================\n\n";


    //p_tcpserver = new QTcpServer(this);
    p_wsserver = new WebSocketServer(this);
    v_clientPort = 9005;
    //p_tcpserver->listen(QHostAddress::Any, 9000);
    p_wsserver->listen(QHostAddress::Any, 9001);
    QObject::connect(p_wsserver, SIGNAL(socketReady()), this, SLOT(connectNewClient()));
}



void ApplicationManager::readConfiguration(const QString &filename)
{
    QFile *file = new QFile(filename);

    v_logger << "Reading configuration file: " << filename << "\n";
    if(!file->exists()){
        v_logger << "The configuration file does not exist, check the path\n";
    }

    QDomDocument doc;
    doc.setContent(file);

    QDomNodeList serviceList = doc.elementsByTagName("service");
    QString serviceName;
    QString commandName;
    QDomElement serviceElement;
    QDomElement commandElement;

    for(int i=0; i<serviceList.count(); i++){
        serviceElement = serviceList.at(i).toElement();
        serviceName = serviceElement.attribute("name");
        QDomNodeList commandList = serviceElement.elementsByTagName("command");
        Service &s = v_serviceFactory.declareService(serviceName);

        v_logger << "New service detected called \"" << serviceName << "\"\n";
        v_logger << "Interface:\n";

        for(int j=0; j<commandList.count(); j++){
            commandElement = commandList.at(j).toElement();
            commandName = commandElement.attribute("name");
            s.addCommand(commandName);

            v_logger << "--- " << commandName << "\n";
        }
        v_logger << "\n";
    }

    QDomNodeList applicationList = doc.elementsByTagName("application");
    QString applicationFilename;
    QString applicationArguments;
    QString applicationSignalPort;
    QStringList arguments;
    // Application unique identifier
    int auid;
    bool ok;
    QString arg;
    QString quotes, not_quotes;
    Application *a;

    v_logger << applicationList.count() << " application(s) detected\n";

    for(int i=0; i<applicationList.count(); i++){
        applicationFilename = applicationList.at(i).toElement().attribute("filename");
        auid = applicationList.at(i).toElement().attribute("auid").toInt(&ok);
        if(!ok){ // If the AUID is null then skip this application
            continue;
        }
        applicationArguments = applicationList.at(i).toElement().attribute("arguments");
        applicationSignalPort = applicationList.at(i).toElement().attribute("signal_port");

        // Reads the parameters, they can be quoted or not
        QRegExp regexp("\"([^\"]*)\"|\\s*([^\"\\s]+)");
        int pos=0;
        arguments.clear();

        while((pos = regexp.indexIn(applicationArguments, pos)) != -1)
        {
            pos += regexp.matchedLength();

            quotes = regexp.cap(1);
            not_quotes = regexp.cap(2);
            arg = (quotes != "") ? quotes : not_quotes ;
            arguments  << arg;
        }

        a = new Application();
        a->setAuid(auid);
        a->setName(applicationFilename);
        a->setArgs(arguments);
        a->setSignalPort(applicationSignalPort.toInt());

        launchApplication(a);
        v_applicationMap[a->getPID()] = a;
    }

    delete file;
}



void ApplicationManager::launchApplication(Application* a)
{
    QStringList args = a->getArguments();
    char* *argsptr = new char*[args.count() + 1];
    int argscount = args.count();
    std::string arg;
    int arg_size;
    char *carg;

    // Creates the args array to pass to execv
    for(int i=0; i<argscount; i++){
        arg = args.at(i).toStdString();
        arg_size = arg.length();
        carg = new char[arg_size+1];
        argsptr[i] = carg;
        strcpy(carg, arg.c_str());
    }
    argsptr[argscount] = NULL;

    // Fork
    int pid = fork();
    // If it is the child
    if(pid == 0){
        if(execvp(a->getName().toStdString().c_str(), (char* const*)argsptr) == -1){
            throw ApplicationManagerException("launchApplication(): The application " + a->getName() + " cannot be launched");
        }
        for(int i=0; i<args.count(); i++){
            delete[] argsptr[i];
        }
        delete[] argsptr;
    // If it is the parent
    }else if(pid > 0){
        a->setPID(pid);
        v_logger << "Application:\n";
        v_logger << "AUID: " << a->getAuid() << "\n";
        v_logger << "Executable: " << a->getName() << "\n";
        v_logger << "Arguments: " << ((QStringList)args.mid(1)).join(" ") << "\n";
        v_logger << "PID: " << a->getPID() << "\n";
        v_logger << "Signal port: " << a->getSignalPort() << "\n";
    }else{
        qDebug("Cannot create the application");
    }
}



// SLOTS

void ApplicationManager::connectNewClient()
{
    qDebug() << "New connected client";
    WebSocket *clientSocket = qobject_cast<WebSocket*>(p_wsserver->nextPendingConnection());
    Application *app = NULL;
    Socket *appSocket = NULL;
    ApplicationConnector *connector;

    connector = new ApplicationConnector(clientSocket, this);

    QMap<int, Application*>::const_iterator it;
    for(it = v_applicationMap.constBegin(); it != v_applicationMap.constEnd(); it++)
    {
        app = *it;
        appSocket = new Socket(this);
        appSocket->connectToHost("127.0.0.1", app->getSignalPort());
        if(!appSocket->waitForConnected(3000))
        {
            qDebug() << "Client not connected to app " << app->getAuid() << " on port " << app->getSignalPort() << " due to:" << appSocket->errorString();
            delete appSocket;
            continue;
        }

        qDebug() << "Client connected to app " << app->getAuid() << "on port" << app->getSignalPort();
        connector->connectApplication(app->getAuid(), appSocket);
    }
    v_connectorList.append(connector);
}


